from sqlalchemy import and_, select
from sqlalchemy.exc import DBAPIError, IntegrityError
from sqlalchemy.orm import joinedload

from src.core.exceptions.common import RepoError
from src.core.interfaces import PromocodeRepo
from src.core.interfaces.promocode import PromocodeReader
from src.core.models.dto import PromocodeDTO
from src.database.converters.promocode import (
    convert_db_model_to_promocode_dto,
    convert_db_model_to_promocode_entity,
    convert_promocode_entity_to_db_model,
)
from src.database.exception_mapper import exception_mapper
from src.database.models import Promocode
from src.database.repositories.base import SQLAlchemyRepo
from src.domain.promocode import PromocodeEntity


class ActivationCodeReaderImpl(SQLAlchemyRepo, PromocodeReader):
    @exception_mapper
    async def get_promocode_by_id(self, promocode_id: int) -> PromocodeDTO | None:
        query = select(Promocode).where(Promocode.id == promocode_id)
        promocode: Promocode | None = await self._session.scalar(query)

        if not promocode:
            return None

        promocode_dto = convert_db_model_to_promocode_dto(promocode)

        return promocode_dto

    @exception_mapper
    async def get_promocode_by_name(
        self, promocode_name: int, shop_id: int
    ) -> PromocodeDTO | None:
        """Ищем промокод по имени и магазину"""

        query = (
            select(Promocode)
            .where(and_(Promocode.name == promocode_name), Promocode.shop_id == shop_id)
            .options(joinedload(Promocode.users))
        )
        promocode: Promocode | None = await self._session.scalar(query)

        if not promocode:
            return None

        promocode_dto = convert_db_model_to_promocode_dto(promocode)

        return promocode_dto


class PromocodeRepoImpl(SQLAlchemyRepo, PromocodeRepo):
    @exception_mapper
    async def acquire_promocode_by_id(
        self, promocode_id: int
    ) -> PromocodeEntity | None:
        query = select(Promocode).where(Promocode.id == promocode_id)
        promocode: Promocode | None = await self._session.scalar(query)

        if not promocode:
            return None

        return convert_db_model_to_promocode_entity(promocode)

    @exception_mapper
    async def update_promocode(self, promocode: PromocodeEntity) -> None:
        db_promocode = convert_promocode_entity_to_db_model(promocode)
        try:
            await self._session.merge(db_promocode)
        except IntegrityError as err:
            self._parse_error(err)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        raise RepoError from err
